package ch.ethz.fcl.metrofont;

import java.awt.image.BufferedImage;
import java.nio.IntBuffer;
import java.util.Vector;

import org.lwjgl.BufferUtils;
import org.lwjgl.examples.spaceinvaders.Texture;
import org.lwjgl.opengl.GL12;

import static org.lwjgl.opengl.GL11.*;

public class FontAtlas {
	Vector<Node> nodes = null;
	private int width;
	private int height;
	private int depth;

	private BufferedImage image;
	private Region black;
	public Texture tex;
	public int tex_id;

	public FontAtlas(int width, int height, int depth) {
		image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

		this.nodes = new Vector<Node>();
		this.width = width;
		this.height = height;
		this.depth = depth;

		Node node = new Node(0, 0, width);
		this.nodes.add(node);

		int n = 4;
		Region r = getRegion(n, n);
		this.black = new Region(r.x + 1, r.y + 1, r.width - 2, r.height - 2);
	}

	public void upload(int target) {
		if (image == null)
			return;
		
		int texture_id = createTextureID();
		
		tex = new Texture(target, texture_id);
		tex.setWidth(image.getWidth());
		tex.setHeight(image.getHeight());
		
		glBindTexture(target, texture_id);
		
		glTexParameteri(target, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
		glTexParameteri(target, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
		glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
	
	private int createTextureID(){
		IntBuffer textureIDBuffer = BufferUtils.createIntBuffer(1);
		glGenTextures(textureIDBuffer);
		return textureIDBuffer.get(0);
	}

	public void showAtlas(boolean show) {
		if (show)
			LoadAndShow.showIcon(image);
	}

	public void setRegion(int minX, int minY, int width, int height,
			BufferedImage subimage) {
		image.getGraphics().drawImage(subimage, minX, minY, null, null);
		image.getGraphics().dispose();
	}

	public int fit(int index, int width, int height) {
		Node node = nodes.get(index);
		int x = node.x;
		int y = node.y;
		int width_left = width;
		int i = index;

		if (x + width > this.width)
			return -1;

		while (width_left > 0) {
			node = nodes.get(i);
			y = Math.max(y, node.y);
			if (y + height > this.height)
				return -1;
			width_left -= node.width;
			i++;
		}
		return y;
	}

	public void merge() {
		for (int i = 0; i < nodes.size() - 1; i++) {
			Node current = nodes.get(i);
			Node next = nodes.get(i + 1);
			if (current.y == next.y) {
				current.setWidth(current.width + next.width);
				nodes.remove(i + 1);
				i--;
			}
		}
	}

	public Region getRegion(int width, int height) {
		int y;
		int best_height = Integer.MAX_VALUE;
		int best_width = Integer.MAX_VALUE;
		int best_index = -1;
		Region region = new Region(0, 0, width, height);

		for (int i = 0; i < nodes.size(); i++) {
			y = fit(i, width, height);
			if (y >= 0) {
				Node node = nodes.get(i);
				if ((y + height < best_height)
						|| (y + height == best_height && node.width < best_width)) {
					best_height = y + height;
					best_index = i;
					best_width = node.width;
					region.setX(node.x);
					region.setY(y);
				}
			}
		}

		if (best_index == -1)
			return new Region(-1, -1, 0, 0);

		Node node = new Node(region.x, region.y + region.height, region.width);
		nodes.add(best_index, node);

		for (int i = best_index + 1; i < nodes.size(); i++) {
			Node current = nodes.get(i);
			Node prev = nodes.get(i - 1);
			if (current.x < (prev.x + prev.width)) {
				int shrink = prev.x + prev.width - current.x;
				current.setX(current.x + shrink);
				current.setWidth(current.width - shrink);
				if (current.width <= 0) {
					nodes.remove(i);
					i--;
				} else
					break;
			} else
				break;
		}
		merge();
		return region;
	}

	public int getWidth() {
		return this.width;
	}

	public int getHeight() {
		return this.height;
	}

	public int getDepth() {
		return this.depth;
	}

	public Region getBlackRegion() {
		return black;
	}

	public void clear() {
		image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		nodes.clear();
		Node node = new Node(0, 0, this.width);
		nodes.add(node);

		int n = 4;
		Region r = getRegion(n, n);
		this.black = new Region(r.x + 1, r.y + 1, r.width - 2, r.height - 2);
	}

	public class Region {
		int x;
		int y;
		int width;
		int height;

		public Region(int x, int y, int width, int height) {
			this.x = x;
			this.y = y;
			this.width = width;
			this.height = height;
		}

		public void setX(int x) {
			this.x = x;
		}

		public void setY(int y) {
			this.y = y;
		}

		public void setWidth(int width) {
			this.width = width;
		}

		public void setHeight(int height) {
			this.height = height;
		}
	}

	class Node {
		int x;
		int y;
		int width;

		public Node(int x, int y, int width) {
			this.x = x;
			this.y = y;
			this.width = width;
		}

		public void setX(int x) {
			this.x = x;
		}

		public void setY(int y) {
			this.y = y;
		}

		public void setWidth(int width) {
			this.width = width;
		}
	}
}
